回到開發選用設計模式的起點,我們會希望有幾個目標可以被滿足:
所以在不管選用什麼模式之前,專案的設計應該要可以符合上面的原則。
在講到為什麼之前,我們先來認識一下 Repository Pattern,這個是來自微軟對於 Repository Pattern 的說明。
Use a repository to separate the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. The business logic should be agnostic to the type of data that comprises the data source layer. For example, the data source layer can be a database, a SharePoint list, or a Web service.
From: learn.microsoft.com
Repository Pattern 的作法其實完美地實作出了 Dependency inversion principle(DIP) 的作法。
所以在實際使用上,可以這樣完成
# 將 Interface 與我們實作出來的 Repository 綁定
class AppServiceProvider extends ServiceProvider
public function register(): void
{
$this->app->bind(PostRepositoryInterface::class, PostEloquentRepostiory::class);
$this->app->bind(AuthorRepositoryInterface::class, AuthorManagementRepostiory::class);
}
}
# 接著我們就可以注入到想要使用的地方
class PostController extends Controller
{
public function __invoke(PostRepositoryInterface $repostiory)
{
// ...
$data = $repostiory->get();
}
}
但是為了滿足可抽換的條件,所以 repostiory 實作的方法不能 return Elquent 的 model,否則這會將 repostiory 直接依賴了 Elquent。而且這樣建立出來的 repostiory 也只是包裝過的 Elquent query 而已。
為此把 Elquent 從這邊分離出來,就才能滿足 repostiory 要符合可抽換的特性。
還記得前面有說到的 YAGNI 嗎? 在開發的過程中應要,盡所能去避免假設未來的功能會需要某種設計模式。使用非預設的工具而且沒有良好文件的輔助時,這都是讓未來專案維護埋下更多的地雷。
Over-engineering 才是殺死眾多專案的維護者的問題。就如以一個專案選用資料庫來說,使用哪一個系列的資料庫的決策通常不太可能說變就變,就算是要變化也會有一定程度的功能上的變動,這樣讓所謂可以直接抽換資料庫的情況或然率接近於不可能會發生。
想想看,在產品的第一階段完成後上線。之後 PM 發現需要繼續追加一些功能,例如:
我們實作了一個方法可以列出某個 author 下所有的 posts 的方法 all()
,現在老闆看到了一些成功的矽谷公司都開始搞付費內容後,想要開始將 post 分成免費用戶可以閱讀的 post,跟 Premium 會員才可以看得到的 post。
這時候我們開始絞盡腦汁開始要接受這個挑戰,下面是有可能的作法:
all()
來區分,像是 bool $isPremium = true
allForPremium()
如果接下來需求進化成,要給尚未註冊
的會員有機會體驗 Premium 功能,所以要可以給訪客看到這些 Premium 會員才看得到的 posts 時你會怎麼作?這會讓你的 Repository 有著如何發展的變化?
這時候才發現到,原來要擴充 Repository 其實有點難度,尤其又要不能在產生 Code smell 的同時兼顧開發功能的步調。最後你會得到巨大的且認知複雜度高的 Repository。
所以在下一篇中,將會提到幾個在 Laravel 中獨特且有效可以使用的方案,其實你一開始就已經在這卻的道路前進了,只是你還沒發現原來開發可以這麼有條理且又有效率。